#include "tile.h"

#include <QPainter>
#include <QDebug>
#include "imagecontrast.h"

QString rgbString(QRgb rgb)
{
  return "(" + QString::number(qRed(rgb)) + "," + QString::number(qGreen(rgb)) + "," + QString::number(qBlue(rgb)) + ")";
}

int colorDistance(QRgb a, QRgb b)
{
  return (qAbs(qRed(a) - qRed(b)) + qAbs(qGreen(a) - qGreen(b)) + qAbs(qBlue(a) - qBlue(b)));
}

QRgb averagePixelsValue(const QImage& img)
{
  qint64 mr=0,mg=0,mb=0;

  for (int x=0;x<img.width();++x)
  {
    for (int y=0;y<img.height();++y)
    {
      QRgb rgb = img.pixel(x,y);
      mr+=qRed(rgb);
      mg+=qGreen(rgb);
      mb+=qBlue(rgb);
    }
  }
  qint64 c = img.width()*img.height();
  return qRgb(mr/c,mg/c,mb/c);
}

Tile::Tile(TileMap* map, const QImage &tile, int x, int y)
  : _tile(tile), _map(map), _x(x), _y(y)
{
  int ww = _map->wallWidth();
  _innerTileAverageValue = averagePixelsValue(tile.copy(ww,ww,tile.width()-ww*2,tile.height()-ww*2));
  _neighborhoodAverageValue = 0;
}

void Tile::computeNeightBorhoodAverageValue()
{
  qint64 mr=0,mg=0,mb=0;
  int mc=0;
  for (int ix=_x-1;ix<=_x+1;++ix)
  {
    for (int iy=_y-1;iy<=_y+1;++iy)
    {
      if (ix < 0 || iy < 0 || ix >= _map->width() || iy >= _map->height()) continue;
      mr+=qRed(_map->tile(ix,iy)->innerTileAverageValue());
      mg+=qGreen(_map->tile(ix,iy)->innerTileAverageValue());
      mb+=qBlue(_map->tile(ix,iy)->innerTileAverageValue());
      ++mc;
    }
  }
  _neighborhoodAverageValue = qRgb(mr/mc,mg/mc,mb/mc);
}

bool Tile::hasLeftWall() const
{
  int ww = _map->wallWidth();
  int tw = _map->tileWidth();
  int th = _map->tileHeight();
  QRgb averageWallValue = averagePixelsValue(_tile.copy(0,ww,ww/2,th-ww*2));
  return colorDistance(averageWallValue,_neighborhoodAverageValue) >= TileMap::DELTA_WALL;
}

bool Tile::hasRightWall() const
{
  int ww = _map->wallWidth();
  int tw = _map->tileWidth();
  int th = _map->tileHeight();
  QRgb averageWallValue = averagePixelsValue(_tile.copy(tw-ww,ww,ww/2,th-ww*2));
  return colorDistance(averageWallValue,_neighborhoodAverageValue) >= TileMap::DELTA_WALL;
}

bool Tile::hasTopWall() const
{
  int ww = _map->wallWidth();
  int tw = _map->tileWidth();
  int th = _map->tileHeight();
  QRgb averageWallValue = averagePixelsValue(_tile.copy(ww,0,tw-ww*2,ww/2));
  return colorDistance(averageWallValue,_neighborhoodAverageValue) >= TileMap::DELTA_WALL;
}

bool Tile::hasBottomWall() const
{
  int ww = _map->wallWidth();
  int tw = _map->tileWidth();
  int th = _map->tileHeight();
  QRgb averageWallValue = averagePixelsValue(_tile.copy(ww,th-ww,tw-ww*2,ww/2));
  return colorDistance(averageWallValue,_neighborhoodAverageValue) >= TileMap::DELTA_WALL;
}

bool Tile::hasWall(Tile::Direction d) const
{
  switch (d)
  {
  case Top:
    return hasTopWall();
  case Bottom:
    return hasBottomWall();
  case Left:
    return hasLeftWall();
  case Right:
    return hasRightWall();
  }

  return false;
}

int TileMap::DELTA_WALL = 14;
int TileMap::DELTA_TILE = 10;
int TileMap::CONTRAST = 500;
int TileMap::BRIGHTNESS = -20;

TileMap::TileMap(int tileWidth, int tileHeight, int wallWidth, const QImage &source)
  : _wallWidth(wallWidth), _tileWidth(tileWidth), _tileHeight(tileHeight)
{
  QImage img = changeContrast(changeBrightness(source,BRIGHTNESS),CONTRAST);
  showImage(img,false);

  _width = img.width()/tileWidth;
  _height = img.height()/tileHeight;

  _tiles = new Tile**[_height];
  for (int i=0;i<source.width()/tileWidth;++i)
    _tiles[i] = new Tile*[_width];

  #pragma omp parallel for
  for (int x=0;x<_width;++x)
  {
    for (int y=0;y<_height;++y)
    {
      _tiles[x][y] = new Tile(this,img.copy(x*_tileWidth,y*_tileHeight,_tileWidth,_tileHeight),x,y);
    }
  }
  #pragma omp parallel for
  for (int x=0;x<_width;++x)
  {
    for (int y=0;y<_height;++y)
    {
      _tiles[x][y]->computeNeightBorhoodAverageValue();
    }
  }
}

void TileMap::highlightWalls(QImage &source) const
{
  QPainter p(&source);

  for (int x=0;x<width();++x)
  {
    for (int y=0;y<height();++y)
    {
      Tile* t = tile(x,y);
      if (t->hasTopWall())
      {
        p.setBrush(QColor(255,0,0,100));
        p.drawRect(x*_tileWidth,y*_tileHeight,_tileWidth,_wallWidth);
      }
      if (t->hasBottomWall())
      {
        p.setBrush(QColor(0,255,0,100));
        p.drawRect(x*200,y*200+_tileHeight-_wallWidth,_tileWidth,_wallWidth);
      }
      if (t->hasLeftWall())
      {
        p.setBrush(QColor(0,0,255,100));
        p.drawRect(x*200,y*200,_wallWidth,_tileHeight);
      }
      if (t->hasRightWall())
      {
        p.setBrush(QColor(255,0,255,100));
        p.drawRect(x*200+_tileWidth-_wallWidth,y*200,_wallWidth,_tileHeight);
      }
    }
  }
}

void TileMap::highlightExits(QImage &source) const
{
  QPainter p(&source);
  p.setBrush(QColor(255,0,0,100));
  typedef QPair<int,int> coord;
  foreach(const coord& c, exits())
  {
    p.drawRect(c.first*_tileWidth,c.second*_tileHeight,_tileWidth,_tileHeight);
  }
}

#include <QStack>

void TileMap::checkTileExits(int x,int y,QStack<Tile *> &stack, QList<QPair<int, int> > &result, Tile::Direction startExitDirection, Tile::Direction endExitDirection) const
{
  Tile* t = tile(x,y);
  stack.push(t);
  if (t->hasWall(startExitDirection))
  {
    stack.clear();
  }
  if (t->hasWall(endExitDirection))
  {
    if (!stack.empty()) stack.pop();
    foreach(Tile* ti, stack) result << QPair<int,int>(ti->_x,ti->_y);
  }
}

QList<QPair<int, int> > TileMap::exits() const
{
  QList<QPair<int,int> > result;
  QStack<Tile*> stack;

  // Top side
  for (int x=0;x<width();++x) checkTileExits(x,0,stack,result,Tile::Right,Tile::Left);

  // Right side
  for (int y=0;y<height();++y) checkTileExits(width()-1,y,stack,result,Tile::Bottom,Tile::Top);

  // Bottom side
  for (int x=width()-1;x>=0;--x) checkTileExits(x,height()-1,stack,result,Tile::Left,Tile::Right);

  // Left Side
  for (int y=height()-1;y>=0;--y) checkTileExits(0,y,stack,result,Tile::Top,Tile::Bottom);

  // Top side (A second time to finish eventually opened areas
  for (int x=0;x<width();++x) checkTileExits(x,0,stack,result,Tile::Right,Tile::Left);

  // Remove duplicates and re-convert the result to list for further iterations
  return result.toSet().toList();
}

